#ifndef ARRAY_EXPR_H
#define ARRAY_EXPR_H

template <class T>
struct expression_const
{
	typedef T return_T;
	
	expression_const( T v ) : val(v) {}
	
	//! evaluation interface 
	return_T operator*() { return val; }
	
	///////////	expression interface ///////////////////////////////////////
	void push( int dim ) {}
	void pop( int dim )  {}
	void load_stride( int dim ) {}
	bool is_unit_stride( int dim ) { return true; }
	int  suggest_stride( int dim ) { return 1; }
	bool is_stride( int dim, int stride ) { return true; }
	bool can_collapse( int outer_dim, int inner_dim ) { return true; }
	void advance() {}
	void advance( int n ) {}
	return_T operator[]( int i ) { return val; }
	return_T fast_read( int i ) { return val; }
	unbound lbound( int dim );
	unbound shape( int dim );
	unbound ordering( int dim );
	////////////////////////////////////////////////////////////////////////
	
private:
	T val;
};

template <class expr_T, class OP>
struct expression_unary
{
	typedef typename expr_T::return_T expr_adp_rtn_T;
	typedef decltype( OP::apply( std::declval<expr_adp_rtn_T>() ) ) return_T;
	
	expression_unary( expr_T expr ) : expr( expr ) {}
	
	//! evaluation interface 
	return_T operator*() { return OP::apply( *expr ); }
	
	///////////	expression interface ///////////////////////////////////////
	void push( int dim ) { expr.push( dim ); }
	void pop( int dim )  { expr.pop( dim ); }
	void load_stride( int dim ) { expr.load_stride( dim ); }
	bool is_unit_stride( int dim ) { return expr.is_unit_stride( dim ); }
	int  suggest_stride( int dim ) { return expr.suggest_stride( dim ); }
	bool is_stride( int dim, int stride ) { return expr.is_stride( dim, stride ); }
	bool can_collapse( int outer_dim, int inner_dim ) { return expr.can_collapse( outer_dim, inner_dim ); }
	void advance() { expr.advance(); }
	void advance( int n ) { expr.advance( n ); }
	return_T operator[]( int i ) { return OP::apply( expr[i] ); }
	return_T fast_read( int i ) { return OP::apply( expr.fast_read( i ) ); }
	
	constexpr typename bounding_checker<lbound_cb, expr_T>::return_T
	lbound( int dim )
	{ return bounding_checker<lbound_cb, expr_T>::get_value( dim, std::make_tuple(expr) ); }
	
	constexpr typename bounding_checker<shape_cb, expr_T>::return_T
	shape( int dim )
	{ return bounding_checker<shape_cb, expr_T>::get_value( dim, std::make_tuple(expr) ); }
	
	constexpr typename bounding_checker<ordering_cb, expr_T>::return_T
	ordering( int dim )
	{ return bounding_checker<ordering_cb, expr_T>::get_value( dim, std::make_tuple(expr) ); }
	////////////////////////////////////////////////////////////////////////
	
private:
	expr_T expr;
};

template <class expr_LT, class expr_RT, class OP>
struct expression_binary
{
	typedef typename expr_LT::return_T return_LT;
	typedef typename expr_RT::return_T return_RT;
	typedef decltype( OP::apply( std::declval<return_LT>(), std::declval<return_RT>() ) ) return_T;
	
	expression_binary( expr_LT expr_left, expr_RT expr_right ) 
	: expr_l( expr_left ), expr_r( expr_right ) {}
	
	//! evaluation interface 
	return_T operator*()
	{ return std::move( OP::apply( *expr_l, *expr_r ) ); }

	
	///////////	expression interface ///////////////////////////////////////
	void push( int dim ) { expr_l.push( dim ); expr_r.push( dim ); }
	void pop( int dim )  { expr_l.pop( dim ); expr_r.pop( dim ); }
	void load_stride( int dim ) { expr_l.load_stride( dim ); expr_r.load_stride( dim ); }
	bool is_unit_stride( int dim ) { return expr_l.is_unit_stride( dim ) && expr_r.is_unit_stride( dim ); }
	int  suggest_stride( int dim ) { return std::max( expr_l.suggest_stride( dim ), expr_r.suggest_stride( dim ) ); }
	bool is_stride( int dim, int stride ) { return expr_l.is_stride( dim, stride ) && expr_r.is_stride( dim, stride ); }
	bool can_collapse( int outer_dim, int inner_dim ) 
	{ 
		return expr_l.can_collapse( outer_dim, inner_dim ) 
			&& expr_r.can_collapse( outer_dim, inner_dim ); 
	}
	void advance() { expr_l.advance(); expr_r.advance(); }
	void advance( int n ) { expr_l.advance( n ); expr_r.advance( n ); }
	return_T operator[]( int i ) { return std::move( OP::apply( expr_l[i], expr_r[i] ) ); }
	return_T fast_read( int i )  { return std::move( OP::apply( expr_l.fast_read(i), expr_r.fast_read(i) ) ); }	
	
	constexpr typename bounding_checker<lbound_cb, expr_LT, expr_RT>::return_T
	lbound( int dim )
	{ return bounding_checker<lbound_cb, expr_LT, expr_RT>::get_value( dim, std::make_tuple(expr_l, expr_r) ); }
	
	constexpr typename bounding_checker<shape_cb, expr_LT, expr_RT>::return_T
	shape( int dim )
	{ return bounding_checker<shape_cb, expr_LT, expr_RT>::get_value( dim, std::make_tuple(expr_l, expr_r) ); }
	
	constexpr typename bounding_checker<ordering_cb, expr_LT, expr_RT>::return_T
	ordering( int dim )
	{ return bounding_checker<ordering_cb, expr_LT, expr_RT>::get_value( dim, std::make_tuple(expr_l, expr_r) ); }
	////////////////////////////////////////////////////////////////////////
	
private:
	expr_LT expr_l;
	expr_RT expr_r;	
};

template <class expr_T>
struct expression
{
	typedef typename std::remove_reference<decltype( *std::declval<expr_T>() )>::type return_T;
	
	expression( expr_T expr ) : expr(expr) {}

	//! evaluation interface 
	return_T operator*()
	{ return std::move( *expr ); }
	
	///////////	expression interface ///////////////////////////////////////
	void push( int dim ) { expr.push( dim ); }
	void pop( int dim )  { expr.pop( dim ); }
	void load_stride( int dim ) { expr.load_stride( dim ); }
	bool is_unit_stride( int dim ) { return expr.is_unit_stride( dim ); }
	int  suggest_stride( int dim ) { return expr.suggest_stride( dim ); }
	bool is_stride( int dim, int stride ) { return expr.is_stride( dim, stride ); }
	bool can_collapse( int outer_dim, int inner_dim ) { return expr.can_collapse( outer_dim, inner_dim ); }
	void advance() { expr.advance(); }
	void advance( int n ) { expr.advance( n ); }
	return_T operator[]( int i ) { return expr[i]; }
	return_T fast_read( int i ) { return expr.fast_read( i ); }
	
	constexpr typename bounding_checker<lbound_cb, expr_T>::return_T 
	lbound( int dim )
	{ return bounding_checker<lbound_cb, expr_T>::get_value( dim, std::make_tuple(expr) ); }
	
	constexpr typename bounding_checker<shape_cb, expr_T>::return_T 
	shape( int dim )
	{ return bounding_checker<shape_cb, expr_T>::get_value( dim, std::make_tuple(expr) ); }
	
	constexpr typename bounding_checker<ordering_cb, expr_T>::return_T 
	ordering( int dim )
	{ return bounding_checker<ordering_cb, expr_T>::get_value( dim, std::make_tuple(expr) ); }		
	////////////////////////////////////////////////////////////////////////

private:
	expr_T expr;
};

//! we only need declaration here
template <typename T, unsigned int DIM> class array;

template <class T>
struct as_expression
{
	typedef typename std::remove_reference<T>::type no_ref_T;
	static constexpr bool is_literal_type = std::is_arithmetic<no_ref_T>::value ||	is_complex<no_ref_T>::value;
	typedef std::integral_constant<bool, is_literal_type> is_literal;
	
	template <class expr_T>
	inline static auto create_impl( const expression<expr_T>& v, std::false_type ) -> expression<expr_T>
	{ return std::move(v); }

	template <typename num_T, unsigned int n_dim>
	inline static auto create_impl( const array<num_T, n_dim>& v, std::false_type ) -> expression<decltype(v.begin())>
	{ return expression<decltype(v.begin())>(v.begin()); }
	
	inline static auto create_impl( const T& v, std::true_type ) -> expression_const<T>
	{ return expression_const<T>(v); }

	//! public interface
	inline static auto create( const T& v ) -> decltype( create_impl(v, is_literal()) )
	{ return create_impl( v, is_literal() ); }
	
	typedef decltype( create( std::declval<T>() ) ) expr_T;
};


#endif //!< ARRAY_EXPR_H